// 类提供用于优化读取、写入以及处理二进制数据的方法和属性。
module.exports = cc.Class({
    properties: {
        BIG_ENDIAN: "bigEndian",//表示多字节数字的最高有效字节位于字节序列的最前面。
        LITTLE_ENDIAN: "littleEndian",//表示多字节数字的最低有效字节位于字节序列的最前面。
        _xd: true,//是否为小端数据。
        _allocated: 2,
        _dataView: null,
        _uInt8Array: null,
        pos: {
            get(){
                return this._pos;
            },
            set(value){
                this._pos = value;
                //this._dataView.byteOffset = value;
            }
        },
        length: {
            set(value){
                if (this._allocated < value) {
                    this._allocated = Math.floor(Math.max(value, this._allocated * 2));
                    this._resizeBuffer(this._allocated);
                } else if (this._allocated_ > value) {
                    this._allocated = value;
                    this._resizeBuffer(this._allocated);
                }
                this._length = value;
            },
            get(){
                return this._length;
            }
        },
        // 字节顺序
        endian: {
            set(endianStr){
                this._xd = (endianStr == this.LITTLE_ENDIAN);
            },
            get(){
                return this._xd ? this.LITTLE_ENDIAN : this.BIG_ENDIAN;

            }
        },
        // 可从字节流的当前位置到末尾读取的数据的字节数
        bytesAvailable: {
            get(){
                return this.length - this.pos;
            }
        },
        _sysEndian: null,
    },

    /**
     * 获取系统的字节存储顺序。
     * @return 字节存储顺序。
     */
    getSystemEndian(){
        if (!this._sysEndian) {
            var buffer = new ArrayBuffer(2);
            new DataView(buffer).setInt16(0, 256, true);
            this._sysEndian = (new Int16Array(buffer))[0] === 256 ? this.LITTLE_ENDIAN : this.BIG_ENDIAN;
        }
        return this._sysEndian;
    },

    /**
     * 创建一个 <code>Byte</code> 类的实例。
     * @param    data 用于指定元素的数目、类型化数组、ArrayBuffer。
     */
    ctor(data = null) {
        this._pos = 0;
        this._length = 0;
        if (data) {
            this._uInt8Array = new Uint8Array(data);
            this._dataView = new DataView(this._uInt8Array.buffer);
            this.length = this._dataView.byteLength;
        } else {
            this._resizeBuffer(this._allocated);
        }
    },

    /**
     * 获取此对象的 ArrayBuffer数据,数据只包含有效数据部分 。
     */
    getBuffer() {
        var rstBuffer = this._dataView.buffer;
        if (rstBuffer.byteLength == this.length) {
            return rstBuffer;
        }
        return rstBuffer.slice(0, this.length);
    },


    _resizeBuffer(len) {
        try {
            var newByteView = new Uint8Array(len);
            if (this._uInt8Array != null) {
                if (this._uInt8Array.length <= len) {
                    newByteView.set(this._uInt8Array);
                } else {
                    newByteView.set(this._uInt8Array.subarray(0, len));
                }
            }
            this._uInt8Array = newByteView;
            this._dataView = new DataView(newByteView.buffer);
        } catch (err) {
            throw "_resizeBuffer err:" + len;
        }
    },

    /**
     * 读取字符型值。
     * @return
     */
    getString() {
        return this.rUTF(this.getUint16());
    },

    /**
     * 从指定的位置读取指定长度的数据用于创建一个 Float32Array 对象并返回此对象。
     * @param    start 开始位置。
     * @param    len 需要读取的字节长度。
     * @return  读出的 Float32Array 对象。
     */
    getFloat32Array(start, len) {
        var v = new Float32Array(this._dataView.buffer.slice(start, start + len));
        this._pos += len;
        return v;
    },

    /**
     * 从指定的位置读取指定长度的数据用于创建一个 Uint8Array 对象并返回此对象。
     * @param    start 开始位置。
     * @param    len 需要读取的字节长度。
     * @return  读出的 Uint8Array 对象。
     */
    getUint8Array(start, len) {
        var v = new Uint8Array(this._dataView.buffer.slice(start, start + len));
        this.pos += len;
        return v;
    },

    /**
     * 从指定的位置读取指定长度的数据用于创建一个 Int16Array 对象并返回此对象。
     * @param    start 开始位置。
     * @param    len 需要读取的字节长度。
     * @return  读出的 Uint8Array 对象。
     */
    getInt16Array(start, len) {
        var v = new Int16Array(this._dataView.buffer.slice(start, start + len));
        this.pos += len;
        return v;
    },

    /**
     * 在指定字节偏移量位置处读取 Float32 值。
     * @return Float32 值。
     */
    getFloat32() {
        var v = this._dataView.getFloat32(this.pos, this._xd);
        this.pos += 4;
        return v;
    },

    getFloat64() {
        var v = _d_.getFloat64(this.pos, this._xd);
        this.pos += 8;
        return v;
    },

    /**
     * 在当前字节偏移量位置处写入 Float32 值。
     * @param    value 需要写入的 Float32 值。
     */
    writeFloat32(value) {
        this.ensureWrite(this.pos + 4);
        this._dataView.setFloat32(this.pos, value, this._xd);
        this.pos += 4;
    },

    writeFloat64(value) {
        this.ensureWrite(this.pos + 8);
        this._dataView.setFloat64(this.pos, value, this._xd);
        this.pos += 8;
    }
    ,
    /**
     * 在当前字节偏移量位置处读取 Int32 值。
     * @return Int32 值。
     */
    getInt32() {
        var float = _d_.getInt32(this.pos, this._xd);
        this.pos += 4;
        return float;
    },

    /**
     * 在当前字节偏移量位置处读取 Uint32 值。
     * @return Uint32 值。
     */
    getUint32() {
        var v = this._dataView.getUint32(this.pos, this._xd);
        this.pos += 4;
        return v;
    },

    /**
     * 在当前字节偏移量位置处写入 Int32 值。
     * @param    value 需要写入的 Int32 值。
     */
    writeInt32(value) {
        this.ensureWrite(this.pos + 4);
        this._dataView.setInt32(this.pos, value, this._xd);
        this.pos += 4;
    },

    /**
     * 在当前字节偏移量位置处写入 Uint32 值。
     * @param    value 需要写入的 Uint32 值。
     */
    writeUint32(value) {
        this.ensureWrite(this.pos + 4);
        this._dataView.setUint32(this.pos, value, this._xd);
        this.pos += 4;
    },

    /**
     * 在当前字节偏移量位置处读取 Int16 值。
     * @return Int16 值。
     */
    getInt16() {
        var us = this._dataView.getInt16(this.pos, this._xd);
        this.pos += 2;
        return us;
    },

    /**
     * 在当前字节偏移量位置处读取 Uint16 值。
     * @return Uint16 值。
     */
    getUint16() {
        var us = this._dataView.getUint16(this.pos, this._xd);
        this.pos += 2;
        return us;
    },

    /**
     * 在当前字节偏移量位置处写入 Uint16 值。
     * @param    value 需要写入的Uint16 值。
     */
    writeUint16(value) {
        this.ensureWrite(this.pos + 2);
        this._dataView.setUint16(this.pos, value, this._xd);
        this.pos += 2;
    },

    /**
     * 在当前字节偏移量位置处写入 Int16 值。
     * @param    value 需要写入的 Int16 值。
     */
    writeInt16(value) {
        this.ensureWrite(this.pos + 2);
        this._dataView.setInt16(this.pos, value, this._xd);
        this.pos += 2;
    },

    /**
     * 在当前字节偏移量位置处读取 Uint8 值。
     * @return Uint8 值。
     */
    getUint8() {
        return this._dataView.getUint8(this.pos++);
    },

    /**
     * 在当前字节偏移量位置处写入 Uint8 值。
     * @param    value 需要写入的 Uint8 值。
     */
    writeUint8(value){
        this.ensureWrite(this.pos + 1);
        this._dataView.setUint8(this.pos, value, this._xd);
        this.pos++;
    },

    /**
     * @private
     * 在指定位置处读取 Uint8 值。
     * @param    pos 字节读取位置。
     * @return Uint8 值。
     */
    _getUInt8(pos) {
        return this._dataView.getUint8(pos);
    },

    /**
     * @private
     * 在指定位置处读取 Uint16 值。
     * @param    pos 字节读取位置。
     * @return Uint16 值。
     */
    _getUint16(pos) {
        return this._dataView.getUint16(this.pos, this._xd);
    },

    /**
     * @private
     * 使用 getFloat32() 读取6个值，用于创建并返回一个 Matrix 对象。
     * @return  Matrix 对象。
     */
    _getMatrix() {
        // TODO matrix没有找到
        //var rst:Matrix = new Matrix(getFloat32(), getFloat32(), getFloat32(), getFloat32(), getFloat32(), getFloat32());
        //return rst;
    },

    /**
     * @private
     * 读取指定长度的 UTF 型字符串。
     * @param    len 需要读取的长度。
     * @return 读出的字符串。
     */
    rUTF(len) {
        // TODO string.formCharCode undefined
        var v = "", max = this.pos + len, c, c2, c3, f = String.fromCharCode;
        var u = this._uInt8Array, i = 0;
        while (this.pos < max) {
            c = u[this.pos++];
            if (c < 0x80) {
                if (c != 0) {
                    v += f(c);
                }
            } else if (c < 0xE0) {
                v += f(((c & 0x3F) << 6) | (u[this.pos++] & 0x7F));
            } else if (c < 0xF0) {
                c2 = u[this.pos++];
                v += f(((c & 0x1F) << 12) | ((c2 & 0x7F) << 6) | (u[this.pos++] & 0x7F));
            } else {
                c2 = u[this.pos++];
                c3 = u[this.pos++];
                v += f(((c & 0x0F) << 18) | ((c2 & 0x7F) << 12) | ((c3 << 6) & 0x7F) | (u[this.pos++] & 0x7F));
            }
            i++;
        }
        return v;
    },

    // River: 自定义的字符串读取,项目相关的内容
    /**
     * 字符串读取。
     * @param    len
     * @return
     */
    getCustomString(len) {
        var v = "", ulen = 0, c, c2, f = String.fromCharCode;
        var u = this._uInt8Array, i = 0;
        while (len > 0) {
            c = u[this.pos];
            if (c < 0x80) {
                v += f(c);
                this.pos++;
                len--;
            } else {
                ulen = c - 0x80;
                this.pos++;
                len -= ulen;
                while (ulen > 0) {
                    c = u[this.pos++];
                    c2 = u[this.pos++];
                    v += f((c2 << 8) | c);
                    ulen--;
                }
            }
        }

        return v;
    },


    /**
     * 清除数据。
     */
    clear() {
        this.pos = 0;
        this.length = 0;
    },

    /**
     * @private
     * 获取此对象的 ArrayBuffer 引用。
     * @return
     */
    __getBuffer() {
        return this._dataView.buffer;
    },

    /**
     * 写入字符串，该方法写的字符串要使用 readUTFBytes 方法读取。
     * @param value 要写入的字符串。
     */
    // utf8-decode
    writeUTFBytes(value) {
        value = value + "";
        for (var i = 0, sz = value.length; i < sz; i++) {
            var c = value.charCodeAt(i);
            if (c <= 0x7F) {
                this.writeByte(c);
            } else if (c <= 0x7FF) {
                //这里要优化,胡高，writeShort,后面也是
                this.writeByte(0xC0 | (c >> 6));
                this.writeByte(0x80 | (c & 63));
            } else if (c <= 0xFFFF) {
                this.writeByte(0xE0 | (c >> 12));
                this.writeByte(0x80 | ((c >> 6) & 63));
                this.writeByte(0x80 | (c & 63));
            } else {
                this.writeByte(0xF0 | (c >> 18));
                this.writeByte(0x80 | ((c >> 12) & 63));
                this.writeByte(0x80 | ((c >> 6) & 63));
                this.writeByte(0x80 | (c & 63));
            }
        }
    },

    /**
     * 将 UTF-8 字符串写入字节流。
     * @param    value 要写入的字符串值。
     */
    writeUTFString(value) {
        var tPos;
        tPos = this.pos;
        this.writeUint16(1);
        this.writeUTFBytes(value);
        var dPos = this.pos - tPos - 2;
        this._dataView.setUint16(tPos, dPos, this._xd);
    },

    /**
     * @private
     * 读取 UTF-8 字符串。
     * @return 读出的字符串。
     */
    readUTFString() {
        var len = this.getUint16();
        return this.readUTFBytes(len);
    },

    /**
     * 读取 UTF-8 字符串。
     * @return 读出的字符串。
     */
    getUTFString() {
        return this.readUTFString();
    },

    /**
     * @private
     * 读字符串，必须是 writeUTFBytes 方法写入的字符串。
     * @param len 要读的buffer长度,默认将读取缓冲区全部数据。
     * @return 读取的字符串。
     */
    readUTFBytes(len = -1) {
        if (len == 0) {
            return "";
        }
        len = len > 0 ? len : this.bytesAvailable;
        return this.rUTF(len);
    },

    /**
     * 读字符串，必须是 writeUTFBytes 方法写入的字符串。
     * @param len 要读的buffer长度,默认将读取缓冲区全部数据。
     * @return 读取的字符串。
     */
    getUTFBytes(len = -1) {
        return this.readUTFBytes(len);
    },

    /**
     * 在字节流中写入一个字节。
     * @param    value
     */
    writeByte(value) {
        this.ensureWrite(this.pos + 1);
        this._dataView.setInt8(this.pos, value);
        this.pos += 1;
    },
    // 控制台打印二进制数据
    getBinaryString(){
        return this._uInt8Array.toString();
    },
    /**
     * @private
     * 在字节流中读一个字节。
     */
    readByte()  {
        return this._dataView.getInt8(this.pos++);
    },

    /**
     * 在字节流中读一个字节。
     */
    getByte() {
        return this.readByte();
    },

    /**
     * 指定该字节流的长度。
     * @param    lengthToEnsure 指定的长度。
     */
    ensureWrite(lengthToEnsure) {
        if (this.length < lengthToEnsure) {
            this.length = lengthToEnsure;
        }
        if (this._allocated < lengthToEnsure) {
            this.length = lengthToEnsure;
        }
    },

    /**
     * 写入指定的 arrayBuffer 对象。
     * @param    arrayBuffer 需要写入的 arrayBuffer 对象。
     * @param    offset 偏移量（以字节为单位）
     * @param    length 长度（以字节为单位）
     */
    writeArrayBuffer(arrayBuffer, offset = 0, length = 0) {
        if (offset < 0 || length < 0) {
            throw "writeArrayBuffer error - Out of bounds";
        }
        if (length == 0) {
            length = arrayBuffer.byteLength - offset;
        }
        this.ensureWrite(this.pos + length);
        var uint8array = new Uint8Array(arrayBuffer);
        var newUint8Array = uint8array.subarray(offset, offset + length);
        this._uInt8Array.set(newUint8Array, this.pos);
        this.pos += length;
    },
});
